package cn.laixueit.service;

import cn.laixueit.client.BrandClient;
import cn.laixueit.client.CategoryClient;
import cn.laixueit.client.GoodsClient;
import cn.laixueit.client.SpecClient;
import cn.laixueit.pojo.Goods;
import cn.laixueit.pojo.SearchRequest;
import cn.laixueit.pojo.SearchResult;
import cn.laixueit.pojo.item.*;
import cn.laixueit.repository.GoodsRepositry;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.math.NumberUtils;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.Operator;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.bucket.terms.LongTerms;
import org.elasticsearch.search.aggregations.bucket.terms.StringTerms;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
import org.springframework.data.elasticsearch.core.query.FetchSourceFilter;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.data.elasticsearch.core.query.SourceFilter;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.io.IOException;
import java.util.*;

@Service
public class SearchService {

    @Autowired
    private BrandClient brandClient;
    @Autowired
    private CategoryClient categoryClient;
    @Autowired
    private GoodsClient goodsClient;
    @Autowired
    private SpecClient specClient;

//    json工具类
    private static  final ObjectMapper MAPPER = new ObjectMapper();


    @Autowired
    private GoodsRepositry goodsRepositry;

    /**
     * 查询方法
     * @param searchRequest 请求参数
     * @return 分页数据
     */
    public SearchResult search(SearchRequest searchRequest) {
        SearchResult result = new SearchResult();

        // 构建高级查询
        NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();

        //构建查询条件
//        MatchQueryBuilder queryBuilder = QueryBuilders.matchQuery("all", searchRequest.getKey()).operator(Operator.AND);
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        boolQueryBuilder.must(QueryBuilders.matchQuery("all", searchRequest.getKey()).operator(Operator.AND));

        if (!CollectionUtils.isEmpty(searchRequest.getFilter())) {
            for (Map.Entry<String, String> entry : searchRequest.getFilter().entrySet()) {
                String key = entry.getKey();
                //如果过滤字段是品牌，过滤字段是brandId
                if(StringUtils.equals("品牌",key)){
                    key = "brandId";
                }else if(StringUtils.equals("分类",key)){
                    key = "cid3";
                }else{
                    key ="specs."+key+".keyword";
                }
                boolQueryBuilder.filter(QueryBuilders.termQuery(key,entry.getValue()));
            }
        }

        nativeSearchQueryBuilder.withQuery(boolQueryBuilder);
        // 构建分页查询
        int startpage = searchRequest.getPage();
        int pagesize = searchRequest.getSize();
        PageRequest pageRequest = PageRequest.of(startpage,pagesize);

        nativeSearchQueryBuilder.withPageable(pageRequest);

        nativeSearchQueryBuilder.withSourceFilter(new SourceFilter() {
            @Override
            public String[] getIncludes() {
                return new String[]{"id","skus","subTitle"};
            }

            @Override
            public String[] getExcludes() {
                return new String[0];
            }
        });


        // 分类查询
        String brandAggName = "brands";
        String categoryAggName = "categorys";
        nativeSearchQueryBuilder.addAggregation(AggregationBuilders.terms(brandAggName).field("brandId"));
        nativeSearchQueryBuilder.addAggregation(AggregationBuilders.terms(categoryAggName).field("cid3"));



//        Page<Goods> goodsPage = goodsRepositry.search(nativeSearchQueryBuilder.build());
        AggregatedPage<Goods> goodsAgg = (AggregatedPage<Goods>) goodsRepositry.search(nativeSearchQueryBuilder.build());


        //根据聚合的品牌id查询对应的品牌集合
        LongTerms brandTerms = (LongTerms) goodsAgg.getAggregation(brandAggName);
        List<Brand> brands = getBrandAggResult(brandTerms);
        result.setBrands(brands);

        // 查询对应的分类名称和分类数据
        LongTerms cidTerms = (LongTerms) goodsAgg.getAggregation(categoryAggName);
        List<Map<String,Object>> categorys =  getCategoryAggReslt(cidTerms);
        result.setCategorys(categorys);


        // 如果分类聚合的结果集等于1 则聚合
        List<Map<String,Object>> specs = null;
        if(categorys.size()==1){
            // 根据分类id和查询条件进行聚合查询
             specs = getParamAggResult((Long)categorys.get(0).get("id"),boolQueryBuilder);
        }


        List<Goods> goodsList = goodsAgg.getContent();

        result.setItems(goodsList);
        result.setTotal(((Long)goodsAgg.getTotalElements()).intValue());
        result.setTotalPage(goodsAgg.getTotalPages());
        result.setSpecs(specs);
        return  result;
    }

    private List<Map<String, Object>> getParamAggResult(Long cid, BoolQueryBuilder queryBuilder) {
        ArrayList<Map<String, Object>> specsMap = new ArrayList<>();
        // 创建自定义查询条件
        NativeSearchQueryBuilder searchQueryBuilder = new NativeSearchQueryBuilder();
        searchQueryBuilder.withQuery(queryBuilder);

        // 查询聚合的的规格参数
        Collection<SpecParam> specParams = specClient.params(null, cid, true,false).getBody();

        // 添加聚合
        specParams.forEach(param->{
            searchQueryBuilder.addAggregation(AggregationBuilders.terms(param.getName()).field("specs."+param.getName()+".keyword"));
        });
        // 只需要聚合结果集，不需要查询结果集
        searchQueryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{},null));

        // 执行查询
        AggregatedPage<Goods> goodsPage = (AggregatedPage<Goods>) goodsRepositry.search(searchQueryBuilder.build());

        // 得到所有的聚合结果集
        Map<String, Aggregation> asMap = goodsPage.getAggregations().asMap();
        for (Map.Entry<String, Aggregation> entry : asMap.entrySet()) {
            HashMap<String, Object> map = new HashMap<>();
            map.put("k",entry.getKey());
            ArrayList<Object> options = new ArrayList<>();
            StringTerms terms = (StringTerms) entry.getValue();
            terms.getBuckets().forEach(bucket -> options.add(bucket.getKeyAsString()));
            map.put("options",options);
            specsMap.add(map);
        }
        return specsMap;
    }


    private List<Map<String, Object>> getCategoryAggReslt(LongTerms cidTerms) {
        ArrayList<Map<String, Object>> list = new ArrayList<>();
        List<LongTerms.Bucket> buckets = cidTerms.getBuckets();
        ArrayList<Long> cids = new ArrayList<>();
        buckets.stream().forEach(bucket -> cids.add(bucket.getKeyAsNumber().longValue()));

        List<String> names = categoryClient.names(cids).getBody();
        for (int i = 0; i < cids.size(); i++) {
            HashMap<String, Object> map = new HashMap<>();
            map.put("id",cids.get(i));
            map.put("name",names.get(i));
            list.add(map);
        }
        return list;
    }

    private List<Brand> getBrandAggResult(LongTerms longTerms){
        ArrayList<Brand> brands = new ArrayList<>();
        List<LongTerms.Bucket> buckets = longTerms.getBuckets();
        for (LongTerms.Bucket bucket : buckets) {
            long bid = bucket.getKeyAsNumber().longValue();
            // 根据品牌id查询你品牌
            ResponseEntity<Brand> brand = brandClient.getBrandById(bid);
            brands.add(brand.getBody());
        }
        return brands;
    }


    public Goods buildGoods(Spu spu) throws IOException {
        Goods goods = new Goods();

        // 查询品牌
        goods.setBrandId(spu.getBrandId());

        // 查询分类id
        goods.setCid1(spu.getCid1());
        goods.setCid2(spu.getCid2());
        goods.setCid3(spu.getCid3());

        // 查询时间
        goods.setCreateTime(spu.getCreateTime());

        // 查询标题
        goods.setSubTitle(spu.getSubTitle());

        //查询id
        goods.setId(spu.getId());

        // 查询标题，品牌名称，分类名称
           // 查询品牌
        ResponseEntity<Brand> brand = brandClient.getBrandById(spu.getBrandId());
            // 查询所有分类名称
        ResponseEntity<List<String>> names = categoryClient.names(Arrays.asList(spu.getCid1(), spu.getCid2(), spu.getCid3()));

        goods.setAll(spu.getTitle()+" " + StringUtils.join(names.getBody()," ")+" "+brand.getBody().getName());

        // 价格集合
        List<Long> prices = new ArrayList<>();
        // 查询所有sku
        Collection<Sku> skus = goodsClient.list(spu.getId()).getBody();
        // 页面显示的所有sku集合,页面只需要一部分数据，所以构建一个新的集合用来保存页面显示的sku集合
        ArrayList<Map<String, Object>> skuMaps = new ArrayList<>();
        skus.forEach(sku -> {
            // 价格添加到集合中
            prices.add(sku.getPrice());
            HashMap<String, Object> skuMap = new HashMap<>();
            skuMap.put("id",sku.getId());
            skuMap.put("title",sku.getTitle());
            skuMap.put("price",sku.getPrice());
            skuMap.put("image",StringUtils.isNotBlank(sku.getImages())?   sku.getImages().split(",")[0] : "" );
            skuMaps.add(skuMap);
        });

        goods.setPrices(prices);


        goods.setSkus(MAPPER.writeValueAsString(skuMaps));


        //查询该spu下的规格参数
        // 因为传递过来的是spu，所以直接根据cpu的3级id查询到对应的可被搜索的参数
        Collection<SpecParam> specParams = specClient.params(null, spu.getCid3(), true,false).getBody();

        //查询商品详情
        SpuDetail spuDetail = goodsClient.detail(spu.getId()).getBody();
        // 获取详情的通用规格并转换为对象
        Map<Long, Object> genericSpecMap = MAPPER.readValue(spuDetail.getGenericSpec(), new TypeReference<Map<Long, Object>>() {});

        // 获取详情的特殊规格
         Map<Long,List<String>> specialSpecMap = MAPPER.readValue(spuDetail.getSpecialSpec(),new TypeReference<Map<Long,List<String>>>(){});

         // 构建一个新的规格参数map
        HashMap<String, Object> paramMap = new HashMap<>();
        specParams.forEach(specParam -> {
            // 判断当前是否是通用参数
            if(specParam.getGeneric()){
                String value = genericSpecMap.get(specParam.getId()).toString();
                // 是否是数值类型
                if(specParam.getNumeric()){
                    // 如果是数值的话，判断该数值落在那个区间
                    value = chooseSegment(value, specParam);
                }
                paramMap.put(specParam.getName(),value);
            }else{
                // 特殊参数直接添加即可
                paramMap.put(specParam.getName(),specialSpecMap.get(specParam.getId()));
            }
        });


        goods.setSpecs(paramMap);

        return goods;
    }

    private String chooseSegment(String value, SpecParam p) {
        double val = NumberUtils.toDouble(value);
        String result = "其它";
        // 保存数值段
        for (String segment : p.getSegments().split(",")) {
            String[] segs = segment.split("-");
            // 获取数值范围
            double begin = NumberUtils.toDouble(segs[0]);
            double end = Double.MAX_VALUE;
            if(segs.length == 2){
                end = NumberUtils.toDouble(segs[1]);
            }
            // 判断是否在范围内
            if(val >= begin && val < end){
                if(segs.length == 1){
                    result = segs[0] + p.getUnit() + "以上";
                }else if(begin == 0){
                    result = segs[1] + p.getUnit() + "以下";
                }else{
                    result = segment + p.getUnit();
                }
                break;
            }
        }
        return result;
    }


    public void createOrUpdateIndex(Long id) {
        Spu spu = goodsClient.findSpuById(id).getBody();

        try {
            Goods goods = buildGoods(spu);
            goodsRepositry.save(goods);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public void deleteIndex(Long id) {
        goodsRepositry.deleteById(id);
    }
}
